#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the version 3 of the GNU Lesser General Public License
#   as published by the Free Software Foundation.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU Lesser General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Copyright (c) NEC Deutschland GmbH, NEC HPC Europe
#
# $Id$

import logging
import time
from threading import Lock
from aggmon.direct_rpc_server import DirectRPCServer
from aggmon.stoppable_thread import StoppableThread
from mon_hierarchy import Hierarchy
from aggmon.aggmon_component import Component

if not "Importer" in globals():
    initialized = False
else:
    initialized = True


class Importer(StoppableThread):
    """
    Generic metrics importer class. The true metric generators should inherit from this.
    Arguments:
      - filter : a string that should eval() to True or False. It can use 'metric'.
      - hierarchy : the hierarchy object describing the group hierarchy,
      - output_channels: array of channels to publish results to,
      - poll_interval_s: interval to run the main poll loop.
    """
    _lock = Lock()
    importers = {}

    def __init__( self, ifilter=None, hierarchy=None, group_channels=None, poll_interval_s=30 ):
        StoppableThread.__init__( self, sleep_s=poll_interval_s )
        self.__filter = ifilter
        self.__hierarchy = hierarchy
        assert isinstance( group_channels, list ), "group_channels must be a list!"
        self.__output_channels = group_channels
        logging.info( "Importer %s will publish to channels %s" %
                      (type(self).__name__,
                       ", ".join( [channel.url for channel in self.__output_channels] ) ) )
        self._counter_lock = Lock()
        self._sent_msgs = 0L

    @staticmethod
    def addImporter( instance, cmd="", iid="" ):
        Importer._lock.acquire()
        if iid in Importer.importers:
            logging.error( "Trying to replace importer id '%s'!?" % iid )
        else:
            logging.info( "Importer '%s' registered." % iid )
            Importer.importers[iid] = { "instance": instance, "cmd": cmd }
        Importer._lock.release()

    @staticmethod
    def delImporter( instance, cmd="", iid="" ):
        Importer._lock.acquire()
        if iid in Importer.importers:
            del Importer.importers[iid]
        else:
            logging.error( "Importer id '%s' not found! Why delete it?" % iid )
        Importer._lock.release()

    @staticmethod
    def getImporters():
        from copy import copy
        return copy(Importer.importers)

    def getHierarchy( self ):
        return self.__hierarchy

    @staticmethod
    def listImporters():
        Importer._lock.acquire()
        importer_ids = Importer.importers.keys()
        Importer._lock.release()
        return importer_ids

    @staticmethod
    def readSentCounter( __id, reset=False ):
        Importer._lock.acquire()
        if not __id in Importer.importers:
            Importer._lock.release()
            return None
        instance = Importer.importers[__id]["instance"]
        Importer._lock.release()
        instance._counter_lock.acquire()
        counter = instance._sent_msgs
        if reset:
            instance._sent_msgs = 0L
        instance._counter_lock.release()
        return time.time(), counter

    def poll( self ):
        '''The poll() method must be implemented in derived classes and deliver (yield)
        requests that need to be published on the output channel.'''
        raise NotImplementedError

    def run( self ):
        while not self._stopevent.is_set():
            for metric in self.poll():

                # apply the filter, if any
                if self.__filter is not None:
                    # skip this iteration if the filter condition failed or throws exception
                    try:
                        filter_result = eval( self.__filter )
                        #print "Filter: %s = %s" % (self.__filter, str( filter_result ) )
                    except Exception, e:
                        # for now, print the exception ;-)
                        print e
                        continue
                    if not filter_result:
                        continue

                topic_array = []

                if self.__hierarchy is not None:
                    # Actually we'd need to find the hierarchy path of the host to whom the metric belongs. Which would fail if
                    # the host is not actually in the hierarchy (and this is legal and wanted!).
                    # Instead, we look up the path of _this_ host, i.e. potentially of a group master that is not in the same group.
                    # TODO: find a better solution for a host topic path.
                    group_topic = Hierarchy.path_to_topic( self.__hierarchy.get_own_host().get_parent_group_name() )
                    if len( group_topic ) > 0:
                        topic_array.append( group_topic )
                try:
                    host_name = metric.host
                except AttributeError:
                    pass
                else:
                    topic_array.append( host_name )

                try:
                    topic_array.append( metric.source )
                except AttributeError:
                    pass

                try:
                    metric_name = metric.name
                except AttributeError:
                    pass
                else:
                    topic_array.append(metric_name)

                topic = ".".join( topic_array )
                for channel in self.__output_channels:
                    self._counter_lock.acquire()
                    self._sent_msgs += 1L
                    self._counter_lock.release()
                    logging.debug( "Importer '%s' publishing %s with topic %s" % (type(self).__name__, repr(metric), topic) )
                    channel.publish( topic, metric )
            self._stopevent.wait(self._sleep_seconds)

if not initialized:
    DirectRPCServer.register_rpc( "readImporterCounter", Importer.readSentCounter )
    DirectRPCServer.register_rpc( "listImporters", Importer.listImporters )


class ImporterComponent(Component):
    def __init__( self, *args, **kwds ):
        Component.__init__( self, *args, **kwds )
        self.allowed_kwds = ["ifilter", "hierarchy", "group_channels", "poll_interval_s"]

    # This has to be implemented for each particular component
    # The code is here only as example for the child implementations and should be removed later.
    def instantiate( self, *args, **kwds ):
        # lose the reference to any previous instance
        self.instance = None
        try:
            logging.debug( "instantiating: %s" % self.name )
            self.instance = Importer( *args, **kwds )
        except Exception, e:
            raise e

    def status( self ):
        if self.state == Component.RUNNING and self.instance.is_running():
            return Component.RUNNING
        else:
            return Component.STOPPED

    def stop( self ):
        if self.instance.is_running():
            self.state = Component.STOPPED
            self.instance.join()
